パソコン活用研究ラピュタへの道(アセンブラ、DOS、Windows、旧型PCの活用研究)
DUMPプログラム(HEXDUMP)の作成 コード編
正直言って、汚いコードなです。その点ご容赦下さい。
ただDUMPするだけの単純なプログラムですが、アセンブラで組むとこんなに長たらしいコード
を書くはめになります。
それでは、コードについて簡単に説明を入れていきます。(かなり、コメントを入れてあるので
まあ、見ていただければ何をやっているかはわかると思います。)
ほぼ、同様の機能を持ったDUMPプログラムをBASICで組んだものが、姉妹サイトの
「パソコン活用研究5番街/DUMPプログラム作成」にありますので、興味あれば、比較してみて
下さい。
(1) プログラム起動
まずは、プログラムの起動部分です。コマンドラインで指定されたファイルをオープンし、オープン
できなかったら、エラーメッセージを表示します。
ここで使っている主なラベル(変数)について説明します。
(A) CMDLN コマンドライン文字列格納用
コマンドライン文字列の取得方法について簡単に説明します。コマンドラインは、MS-DOSにより
プログラムの80H番地からセットされます。
例えば、
A>hexdump hexdupm
とすると、プログラムの80Hから以下のように格納されます。
まず80Hにコマンドラインで与えられた文字列の文字数が格納されます。
81H以降に、コマンドライン文字列が格納されます。
番地 | 80H | 81H | 82H | 83H | 84H | 85H | 86H | 87H | 88H | 89H |
ラベルでの表記 | cdml | cdml+1 | cdml+2 | cdml+3 | cdml+4 | cdml+5 | cdml+6 | cdml+7 | cdml+8 | cdml+9 |
データ | 8 | ' ' | 'h' | 'e' | 'x' | 'd' | 'u' | 'm' | 'p' | 0DH |
備考 | 文字数 | 改行コード |
;------------------- ; Hexdump ver0.0.1 ;------------------- CODE SEGMENT ASSUME CS:CODE, DS:CODE,ES:CODE,SS:CODE ORG 80H CMDLN DB 128 DUP(0) ORG 100H START: MOV AH,9 ;INT21Hシステムコール AH=9 :文字列の表示 MOV DX, OFFSET STARTMES ;DX=文字列先頭アドレス INT 21H ;スタートメッセージ表示 MOV BL, CMDLN ;BL=コマンドライン文字数 MOV BH,0 MOV CMDLN[BX+1],0 ;コマンドライン(=ファイル名)のASCIZ化 (文字列の末尾に0を追加) MOV AH,3DH ;INT21Hシステムコール AH=3DH:ファイルオープン MOV AL,0 ;AL=0 :読出しモード MOV DX,OFFSET CMDLN+2 ;ファイル名をDXにセット INT 21H ;ファイルのオープン 正常にファイルオープンできた場合はAXにファイルハンドル JC OERROR1 ;キャリーフラグがセットされた場合はエラー(AXにエラーコード)−>OERROR1へ MOV FHANDLE, AX ;FHANDLE=AX(ファイルハンドル) MOV AH,3EH ;INT21Hシステムコール AH=3EH :ファイルクローズ MOV BX,FHANDLE ;BX:ファイルハンドル INT 21H ;ファイルハンドルのクローズ |
(2) コマンド入力
起動後、コマンド入力待ちの状態になります。コマンドが入力されるまで、ループします。
コマンドの詳細は「DUMPプログラムの作成 企画編」の2)コマンド を参照して下さい。
;-------------------------------------------------------- ;コマンド入力ルーチン ;-------------------------------------------------------- CONL: CALL PRINTCRLF ;改行ルーチンのコール MOV AH,2 MOV DL,'-' INT 21H ;コマンド入力待ち '-'の表示 MOV AH,0AH ;INT21Hシステムコール AH=0AH:キーボード入力 MOV DX,OFFSET CONBUF ;DX:入力バッファアドレス INT 21H ;キーボードからのコマンド入力 CMP CONBUF[2],'D' ;コマンドの判定 JZ DUMPL ;'D'ならダンプ表示へ CMP CONBUF[2],'Q' JZ FIN1 ;'Q'なら終了へ CMP CONBUF[2],'C' JZ CHECK ;'C'ならバイナリデータ検索へ JMP CONL ;コマンド入力へ戻る CHECK: CALL CHECKL ;バイナリデータ検索ルーチンコール JMP CONL FIN1: INT 20H ;終了 |
(3) ダンプ表示
ダンプ表示をします。表示中も常にキーボードからの入力をチェックしていて、’1’が押されると
一時中断。’2’で再開。’3’で終了します。
ここの部分で使用されている主なラベルについて説明します。
(A) CONDF ダンプ表示の状態 0 ダンプ表示中 1
一時中断状態
(B) KANF 前の行の行末が漢字だったかどうかを表すフラグ
0 漢字ではない
1 漢字ではない(1バイト目は漢字の可能性のあるコード番号)
2 漢字だった
フラグの状態によって、行の先頭の処理が変わります。
処理の詳細は、「DUMPプログラムの作成 企画編」の(3)キャラクタ表示の処理に書いてあります
ので参照して下さい。
(C) LINBUF[ ] キャラクタ表示用です。表示できないデータには、 「.」を表示します。
(D) CHECKBUF 検索データ
(E) CHECKF ダンプ表示の色 1 赤(検索データと一致したデータ) 2 白
コードの説明。
@ 表示の色の変更
表示する色を変更する方法にはいくつかありますが、ここではエスケープシーケンスによる安直な
方法を使っています。
MOV AH,9
MOV DX,OFFSET REDC ;REDCは表示を赤にするエスケープシーケンス
INT 21H ;表示を赤に変更
ラベル REDCは、表示を赤にするエスケープシーケンスです。
REDC DB 1BH,'[31m$'
エスケープシーケンスについて、詳細はエスケープシーケンスを参照して下さい。
;----------------------------------------------------- ;ダンプ表示 ;----------------------------------------------------- DUMPL: MOV AH, 3DH ;INT21Hシステムコール AH=3DH:ファイルオープン MOV AL,0 ;AL=0 読出しモード MOV DX, OFFSET CMDLN+2 ;DX:パス名の先頭アドレス INT 21H ;ファイルオープン MOV ADDRESS, WORD PTR 0 JMP LOOPL OERROR1: JMP OERROR LOOPL: MOV AH,6 MOV DL,0FFH INT 21H ;キーボードから1文字入力 ALに入力データがリターンされる CMP AL,'1' ;押されたキーの判定 JZ LSTOP ;1なら一時中断 CMP AL, '2' JZ LRESTART ;2なら再開 CMP AL, '3' JZ FINISH ;3なら終了 JMP LOOP1 FINISH: INT 20H LSTOP: MOV CONDF,1 ;CONDFフラグを1にする。(一時中断状態) JMP LOOP1 LRESTART: MOV CONDF,0 ;CONDFフラグを0にする。(ダンプ実行) LOOP1: CMP CONDF,1 JZ LOOPL ;CONDFフラグが1ならLOOPLに戻り、ループ(待ち状態)継続 LOOPA: CALL PRINTCRLF CALL PRINTADR ;アドレス表示プロシージャのコール CALL LINEBUFINIT ;キャラクタ表示用LINBUF[]の初期化 MOV BX,0 CMP KANF,0 ;前の行の行末は漢字ではない(KANF=0)−−>LOOPBへ JZ LOOPB ;-------------------------------------------------------------------------- ;前の行の行末は漢字ではない(KANF=1)の場合 KANBUF−>AL−>LINEBUF[0] ;前の行末が漢字だった(KANF=2)場合 LINEBUF[0]には「 」(空白)を入れSKIPへ ;--------------------------------------------------------------------------- MOV AL,KANBUF CMP KANF,1 JZ SKIP1 MOV LINEBUF[0],' ' MOV KANF,0 JMP SKIP SKIP1: MOV KANF,0 MOV LINEBUF[0],AL JMP SKIP LOOPB: CALL GETC ;1バイト読み込み。ALに入力されたデータを保持 JNZ EOF ;GETCプロシージャコールの結果、Zフラグ=0(読み込まれたデータがない)ならEOFへ MOV LINEBUF[BX],AL ;LINEBUF[BX]=AL SKIP: CMP AL,CHECKBUF ;AL(入力データ)とCHECKBUF(検索データ)の比較 JNZ NCOLOR ;一致していなければNCOLORへ、一致していれば、表示色を赤に MOV AH,9 MOV DX,OFFSET REDC ;REDCは表示を赤にするエスケープシーケンス ...@ INT 21H ;表示を赤に変更 MOV CHECKF,1 ;CHECKF 表示の色フラグ 1 赤 0 白 MOV AL,CHECKBUF NCOLOR: CALL PUTHEX ;16進数での表示 CMP CHECKF,1 ;CHECKF=1(表示が赤)なら−>白に戻しておく JNZ WHIT ;CHECKF=0(表示白)なら以下飛ばして、WHITへ MOV AH,9 MOV DX,OFFSET WHITEC ;WHITEC 表示を白にするエスケープシーケンス INT 21H ;表示を白に変更 MOV CHECKF,0 ;CHECKFも0に戻しておく WHITE: INC BX ;BX←BX+1 CMP BX, 10H ;BXが16未満(1行=16文字分未処理なら)LOOPBに戻って次のデータの処理継続 JB LOOPB CALL PUTC ;キャラクタ表示のコール ADD ADDRESS,16 ;ADDRESSを16加算 JMP LOOPL |
(4) エラー処理、EOF処理、CLOSE処理
OERRORはファイルが正常にオープンできなかったときの処理です。このHEXDUMPでは、エラーメッセージ
を表示してプログラム終了します。
RERRORは、ファイルの読み込みエラーの時の処理です。エラーメッセージを表示し、CLOSE処理に
飛びます。
EOFは、EOF(ファイルの読み込み終了)の時の、最終行のキャラクタ表示の行末処理です。
最終データ(LINEBUF[BX])の後に、0と$を付加してからPUTC(キャラクタ表示ルーチン)をコールしています。
CLOSEはファイルのクローズ処理です。
OERROR: ;エラーメッセージを表示して終了 MOV DX, OFFSET OERRMES MOV AH,9 INT 21H INT 20H RERROR: MOV DX, OFFSET RERRMES MOV AH,9 INT 21H JMP CLOSE EOF: ;EOF時の行末処理(0、$の付加)->キャラクタ表示 MOV LINEBUF[BX+1],0 MOV LINEBUF[BX+2],'$' CALL PUTC CLOSE: ;ファイルクローズ処理 MOV AH, 3EH MOV BX,FHANDLE INT 21H JMP CONL |
(5) PRINTADR PROC アドレスの表示
アドレスの表示をします。
PUTHEX(バイナリデータ->16進数のキャラクタ表示 変換ルーチン)では、レジスターALのデータを
16進数としてキャラクタ表示します。従って、アドレス(OOOOH〜FFFFH)を上位桁、下位桁に分けて、
ALに入れて、PUTHEXをコールしています。
PRINTADR PROC ;アドレス部の表示 MOV AX,ADDRESS MOV AL,AH ;AL<-アドレスの上位桁(AH) CALL PUTHEX ;まず、上位桁を16進表示 MOV AX,ADDRESS CALL PUTHEX ;アドレスの下位桁の16進表示 MOV DL,':' MOV AH,2 INT 21H ;':'の表示 RET PRINTADR ENDP |
(6) CHECKL PROC 検索データ(Cコマンド)処理
Cコマンドの処理をします。
コードの説明
(A) 入力された16進数(キャラクタコード)->バイナリー値変換
Cコマンドは以下のように与えられるます(16進数で7Bを検索したい場合)
-C7B
すると、CONBUF[3]には'7'のキャラクタコード37Hが入ります。CONBUF[4]には'B'のキャラクタコード
42Hが入ります。
この2つのキャラクタコードを処理して、最終的にCHECKBUFというラベルにバイナリー値として7BHが
入るように処理します。
簡単に処理の手順を追って見ます。
@ MOV AL, CONBUF[3]
CALL HENKAN
HENKANルーチンをコールします。これでレジスターAL(=CONBUF[3])に入っている37H(キャラクター'7')
は、バイナリー値07Hとして戻されます。
A SAL AL,1 ;4回左シフトして、上位4ビットに値をシフト
4回左シフトすることにより、07Hは70Hになります。
00000111 =07H
01110000 =70H <---4回左シフトした。
B MOV AH,AL
上位桁の情報はとりあえず、AHに移しておきます。
C MOV AL,CONBUF[4] ;CONBUF[4]:検索したいデータの下位バイト(キャラクタコード)
CALL HENKAN
下位桁('B')も同様に処理し、ALにバイナリー値で0BHが入ります
D ADD AH,AL
上位桁 AH(=70H)+AL(=0BH)でキャラクターコード −>バイナリー値への変換完了です。
(B) HENKANルーチン
ここが、キャラクタコード−>バイナリー値変換を実際に1バイト単位で行っている部分です。
アセンブラでプログラムを組む場合、必ず必要になる変換です。何をやっているのかをよく理解して
下さい。
逆のパターンで、バイナリー値をキャラクタ表示(16進数)するコードの詳細な説明が
DEBUGで機械語プログラムその2にあげてあります。そちらを参照していただければ、ここのコードの
意味が理解できると思います。
コード自体は CMP(比較) JMP(分岐) SUB(減算)だけですから簡単ですね。
CHECKL PROC ;検索データ(Cコマンド)処理 MOV AL,CONBUF[3] ;CONBUF[3]:検索したいデータの上位バイト(キャラクタコード) CALL HENKAN ;キャラクタコード->バイナリー値 SAL AL,1 ;4回左シフトして、上位4ビットに値をシフト SAL AL,1 SAL AL,1 SAL AL,1 MOV AH,AL ;AH<-AL AHに検索したいデータの上位バイトがバイナリー値(上位4ビット)としてセットされた MOV AL,CONBUF[4] ;CONBUF[4]:検索したいデータの下位バイト(キャラクタコード) CALL HENKAN ;ALに検索したいデータの下位バイトがバイナリー値(下位4ビット)としてセットされた ADD AH,AL ;AH←AH(上位バイト)+AL(下位バイト) MOV CHECKBUF,AH ;CHECKBUF:検索データ RET ;キャラクタコード−>バイナリデータ変換 HENKAN: CMP AL,30H JB CHECKEND ;キャラクタコード30H未満はCHECKENDへ CMP AL,39H JBE ZERO ;30H〜39H(数字の0〜9)はZEROへ CMP AL,41H JB CHECKEND CMP AL,46H ;41H〜46H(アルファベット小文字a〜f)はHEXへ JBE HEX JMP CHECKEND ZERO: ;30H(=48)を引いて、数値(0〜9)に変換 SUB AL,30H RET HEX: ;37Hを引いて、数値(aH = 10 〜 fH =16)に変換 SUB AL,37H RET CHECKEND: MOV AL,0 RET CHECKL ENDP |
(7) PUTHEX PROC
バイナリーデータ−>キャラクタコード変換
PUTHEXは、バイナリー値を実際にダンプ表示する機能の部分であり、
@バイナリーデータ−>キャラクタコード変換を行い ((6)とは逆の作業です)
A表示する
という作業をしています。
簡単にコードを追ってみましょう。例えば、'7B'というバイナリー値を表示する場合を考えます。
@ AND AL,0F0H ;下位桁をマスク
まず、上位桁'7'を取り出すために、下位桁をマスクします。
AL(7BH) 01111011
AND AL, 0F0H 11110000
-----------
01110000
これで、レジスタALに'70'がとりこまれます。
A SHR AL,1 ;4回右シフトして
4回右シフトして、'70' −> '07' とします。
B HEXDISPコール
HEXDISPでは、0〜9は30H足して、aH=10 〜fH=16
は37H足してキャラクタコードに変換して
DISP(実際の表示のシステムコール)へジャンプします。
今回は、7なので30Hを足して、37H('7'のキャラクタコード)とします。これで、モニター上に7が表示
されます。
C以下、下位桁の処理を行います。
この、バイナリーデータ->キャラクタコード変換 はよく使うコードなので、理解しておく必要があります。
詳細な説明が、DEBUGで機械語プログラムその2にありますので、参照して下さい。
;---------------------------------------------------------------- ;16進数での表示 バイナリデータ−>キャラクタコード ;---------------------------------------------------------------- PUTHEX PROC MOV CHARA,AL ;CHARA=AL(入力データ) AND AL,0F0H ;下位桁をマスク SHR AL,1 ;4回右シフトして SHR AL,1 SHR AL,1 SHR AL,1 CALL HEXDISP ;上位桁表示 MOV AL,CHARA AND AL,0FH ;上位桁マスク CALL HEXDISP ;下位桁表示 MOV DL,' ' MOV AH,2 INT 21H ;空白を1個表示 RET ;-------------------------------------------------------------------------- ;16進数として表示。 ;0〜9は30H足して、aH=10 〜fH=16 は37H足してキャラクタコードに変換してDISPへ ;-------------------------------------------------------------------------- HEXDISP: CMP AL,9 JA TOHEX MOV DL,30H ADD DL,AL JMP DISP TOHEX: MOV DL,37H ADD DL,AL DISP: MOV AH,2 ;INT21Hシステムコール AH=2 文字の出力 INT 21H ;文字の出力 DL=キャラクタコード RET PUTHEX ENDP |
(8) PRINTCRLF PROC 改行
改行します。 CR(0DH)+LF(0AH)の出力です。
PRINTCRLF PROC ;改行 MOV DL,0DH MOV AH,2 INT 21H MOV DL,0AH MOV AH,2 INT 21H RET PRINTCRLF ENDP |
(9) GETC PROC
1バイトデータ取得
GETC PROCは(3)ダンプ表示の部分から呼び出され、1バイトのデータを取得します。
読み出した1バイトのデータは、ALに入れてリターンします。
RERROR1:
ニアジャンプでは届かないので、ここでジャンプを中継しています。(GETC
PROCとはまったく
関係なし)
;------------------------------------------------------------ ;1バイトデータ取得 ;------------------------------------------------------------ GETC PROC PUSH BX MOV BX,FHANDLE ;BX=ファイルハンドル MOV AH,3FH ;INT21Hシステムコール AH=3FH:ファイル読出し MOV DX, OFFSET BUF ;DX=入力バッファ先頭アドレス(OFFSET BUF) MOV CX,1 ;CX 読み込むバイト数 INT 21H ;ファイルの読出し JC RERROR1 CMP AX,CX ;AX:読み込まれたバイト数 CX:読み込むバイト数の比較−>メインルーチンで判定 MOV AL,BUF ;読み込んだキャラクタ(=BUF)はALに入れる POP BX RET GETC ENDP RERROR1: JMP RERROR |
(10) LINBUFINIT PROC
LINEBUF[ ]を初期化するルーチンです。
ここでは、LOOPの使い方を覚えて下さい。レジスタCXに繰り返し回数を設定します。書式は以下の通りです。
LOOP ラベル名
CXが0になまでラベル名に戻りループします。この時CXは1づつ減算されます。
LINEBUFINIT PROC ;キャラクタ表示用LINEBUF[]の初期化 MOV CX,16 INITS: ;LINEBUF[]を0に初期化 MOV SI,CX MOV LINEBUF[SI],0 LOOP INITS ;CX <--CX-1 CX=0になるまでループ(16回) RET LINEBUFINIT ENDP |
(11) PUTC PROC
キャラクタ表示のルーチンです。
ここの処理は、若干面倒です。処理のフローについては、DUMPプログラム作成 企画編に詳しく書きました
ので、参照して下さい。また、HEXDUMPフローチャートも参照して下さい。
;------------------------------------------------ ;キャラクタ表示 ;------------------------------------------------ PUTC PROC MOV BX,0 PUTCS: MOV AL,LINEBUF[BX] CMP AL,20H JB NOASCI ;キャラクタコードが20H未満ならNOASCI(文字でない)の処理へ CMP AL,24H JZ NOASCI CMP AL,80H JB ASCI ;20Hから7FHはASCI(1バイト文字処理)へ CMP AL,9FH JBE KANJI ;80Hから9FHはKANJI(漢字処理)へ CMP AL,0E0H JB ASCI ;A0HからDFHはASCI(1バイト文字処理)へ CMP AL,0FCH JBE KANJI ;E0HからFCHはKANJI(漢字処理)へ NOASCI: MOV LINEBUF[BX],'.' JMP ASCI KANJI: CMP BX,15 JZ KANJI15 ;行末(BX=15)ならKANJI15(行末漢字処理)へ MOV AH,LINEBUF[BX+1] ;2バイト目(LINEBUF[BX+1])をAHに読み込み CALL ISKANJI2 ;2バイト目が漢字コードかどうかの判定へ CMP KAN2,1 ;KAN2=1 漢字コードなら JZ KANTRUE ;KANTRUE(漢字表示)へ MOV LINEBUF[BX],'.' ;漢字でないなら'.'の表示 JMP ASCI KANTRUE: INC BX JMP ASCI KANJI15: ;行末の場合の漢字(2バイト文字)の処理 CALL GETC ;次の行の先頭の1バイトを読み込み MOV AH,AL CALL ISKANJI2 ;2バイト目が漢字コードかどうかの判定 CMP KAN2,1 JZ KANTRUE15 ;KAN2=1 漢字ならKANTRUE15(行末の漢字表示)へ MOV KANF,1 ;漢字でなければKANF=1に設定 MOV KANBUF,AH ;先読みした次の行の先頭バイト(AH)をKANBUFに保存。次の行の処理で使う MOV LINEBUF[15],'.' MOV LINEBUF[16],' ' JMP ASCI KANTRUE15: MOV LINEBUF[16],AH MOV KANF,2 ;KANF=2 行末は漢字だった。 MOV KANBUF,AH ASCI: INC BX CMP BX,16 JAE PUTCEND ;1行分(16データ)処理したらPUTCENDへ JMP PUTCS ;16データ未満ならPUTCSに戻って処理を継続 PUTCEND: MOV AH,9 MOV DX, OFFSET LINEBUF[0] INT 21H ;1行分のキャラクタ表示 RET ;---------------------------------------------------------- ;2バイト目が漢字コードかどうかの判定 ;40H〜7EH 80H〜FCHなら漢字 −>KAN2フラグ=1 ;---------------------------------------------------------- ISKANJI2: CMP AH,40H JB FALSE CMP AH, 7EH JBE TRUE CMP AH,80H JB FALSE CMP AH,0FCH JBE TRUE FALSE: MOV KAN2,0 RET TRUE: MOV KAN2,1 RET PUTC ENDP |
(12) データ定義、コード終了
STARTMES DB 'HEXDUMP ver0.1 <制作 河西 毅>',0Dh,0AH DB 'コマンド',0DH,0AH DB 'D バイナリーダンプ 以下のサブコマンドがあります',0DH,0AH DB '1 中断 2 再開 3 終了',0DH,0AH DB 'Q HEXDUMP終了',0DH,0AH DB 'C バイナリデータ検索 指定したデータが赤色で表示。 指定例)C38, C7E',0DH,0AH DB '全て大文字で指定して下さい',0DH,0AH,'$' OERRMES DB 'ファイルが開けません。コマンドラインに正しいファイル名を指定して下さい',0DH,0AH,'$' RERRMES DB 'データ読み込みエラー',0DH,0AH,'$' REDC DB 1BH,'[31m$' WHITEC DB 1BH,'[m$' FHANDLE DW 0 ADDRESS DW 0 CHARA DB 0 BUF DB 0 KANF DB 0 KANBUF DB 0 KAN2 DB 0 CONDF DB 0 CONBUF DB 4,5 DUP(0) CHECKBUF DB 0 CHECKF DB 0 LINEBUF DB 17 DUP(0),'$' CODE ENDS END START |
(13) 終わりに
機能としては、ダンプするだけのシンプルなプログラムですが、アセンブラで組むと、結構たいへんな
作業になります。HEXDUMPはエラートラップなどはかなりオオザッパなので、そこらへん強化していくと
もっと面倒なコードになります。